package com.zj.learn.sudoku;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author xi.yang
 * @create 2021-07-31 16:33
 **/
public class Sodo {
    private int index;
    private Set<Integer> val;
    /**
     * 九宫格
     */
    private Set<Sodo> gridGroup;
    /**
     * 同列
     */
    private Set<Sodo> colGroup;
    /**
     * 同行
     */
    private Set<Sodo> rowGroup;

    public Set<Integer> getVal() {
        return val;
    }

    public void setVal(Set<Integer> val) {
        this.val = val;
    }

    public void initGroup(Sodo[] all) {
        int r = index / 9;//在第几行 0-8
        int c = index % 9;//在第几列 0-8
        rowGroup = new HashSet<>();
        colGroup = new HashSet<>();
        gridGroup = new HashSet<>();
        for (int i = 0; i < 81; i++) {
            // 排除自己
            if (i == index) {
                continue;
            }
            // 同行
            if (i / 9 == r) {
                rowGroup.add(all[i]);
            }
            // 同列
            if (i % 9 == c) {
                colGroup.add(all[i]);
            }
            // 九宫格
            if (r / 3 == ((i / 9) / 3) && c / 3 == ((i % 9) / 3)) {
                gridGroup.add(all[i]);
            }
        }
    }

    public Sodo(int index, char c) {
        this.index = index;
        val = new HashSet<>();
        if ('x' == c) {
            for (int i = 0; i < 9; i++) {
                val.add(i + 1);
            }
        } else {
            val.add(Integer.parseInt("" + c));
        }
    }

    public boolean isOk() {
        return val.size() == 1;
    }

    public int getOneVal() {
        return val.iterator().next();
    }

    public void setOneVal(int num) {
        val.clear();
        val.add(num);
        colOpSodo();
    }

    public int getIndex() {
        return index;
    }

    @Override
    public String toString() {
        return val.stream().map(x -> "" + x).collect(Collectors.joining(",", "[", "]"));
    }

    public String vVal() {
        if (val.size() == 1) {
            return val.iterator().next() + "";
        } else {
            return "x";
        }
    }

    /**
     * 单个格子确定值后修改其他格子
     */
    public void colOpSodo() {
        if (!isOk()) {
            return;
        }
        // 其他group移除本格子固定的值
        for (Sodo sodo : rowGroup) {
            SodoOp.sodoValRm(sodo, getOneVal());
        }
        for (Sodo sodo : colGroup) {
            SodoOp.sodoValRm(sodo, getOneVal());
        }
        for (Sodo sodo : gridGroup) {
            SodoOp.sodoValRm(sodo, getOneVal());
        }
    }

    /**
     * 单个格子的值有变化但没确定修改其他格子
     */
    public void changeVal() {
        numEqualsGrids(gridGroup);
        numEqualsGrids(colGroup);
        numEqualsGrids(rowGroup);
        // 同一个group只有一个格子包含某个值的可以定下来
        doChangeVal(rowGroup);
        doChangeVal(colGroup);
        doChangeVal(gridGroup);
    }

    /**
     * 只有n个格子正好只能是n个值的确定下来
     *
     * @param group
     */
    private void numEqualsGrids(Set<Sodo> group) {
        Map<String, Set<Sodo>> nums = new HashMap<>();
        addNums(nums, this);
        for (Sodo sodo : group) {
            addNums(nums, sodo);
        }
        nums.forEach((k,v)->{
            int num = v.size();
            if (num > 1 && num < 9) {
                if (num == Integer.parseInt(k.substring(0,1))) {
                    for (Sodo sodo : group) {
                        if (!SodoOp.containsSodo(v, sodo)) {
                            for (Integer n : v.iterator().next().getVal()) {
                                SodoOp.sodoValRm(sodo, n);
                            }
                        }
                    }
                }
            }
        });
    }

    private void addNums(Map<String, Set<Sodo>> nums, Sodo sodo) {
        String key = sodo.getVal().size() + "_" + sodo.getVal().stream().sorted().map(n -> "" + n).collect(Collectors.joining("_"));
        Set<Sodo> sodos = nums.get(key);
        if (sodos == null) {
            sodos = new HashSet<>();
        }
        sodos.add(sodo);
        nums.put(key, sodos);
    }

    private void doChangeVal(Set<Sodo> group) {
        Map<Integer, Integer> map = getValMap();
        for (Sodo sodo : group) {
            for (Integer integer : sodo.getVal()) {
                if (map.containsKey(integer)) {
                    map.put(integer, -1);
                } else {
                    map.put(integer, sodo.getIndex());
                }
            }
        }
        map.forEach((k,v)->{
            if (v != -1) {
                if (getIndex() == v) {
                    setOneVal(k);
                }
                for (Sodo sodo : group) {
                    if (sodo.getIndex() == v) {
                        sodo.setOneVal(k);
                    }
                }
            }
        });
    }

    private Map<Integer, Integer> getValMap() {
        Map<Integer, Integer> map = new HashMap<>();
        for (Integer integer : getVal()) {
            map.put(integer, getIndex());
        }
        return map;
    }
}
